Skip to content

淺談 .NET 預設 Logger 及其優化技巧

TLDR

  • .NET 預設已整合 Console、Debug 與 EventSource 等 Provider,可透過 builder.Logging 進行自訂。
  • 使用 appsettings.json 可靈活控制不同命名空間與 Provider 的日誌等級,支援萬用字元 *
  • 針對高頻率或高資源消耗的日誌,應優先使用 logger.IsEnabled 進行預先檢查,避免無效運算。
  • 結構化日誌(Structured Logging)能避免字串串接效能損耗,並利於 ELK 等系統解析。
  • 推薦使用 [LoggerMessage] Source Generator,它能自動產生強型別委派,消除 object[] 配置與 Boxing 成本,且內建 IsEnabled 檢查。

範例專案

本文的可執行範例:CloudyWing/DotNetLoggingSample

紀錄等級

在開發與維護時,應根據情境選擇合適的 Log Level,以利於生產環境的過濾與除錯。

等級方法描述
追蹤 (Trace)0LogTrace最詳細訊息,預設停用,不應在生產環境啟用。
偵錯 (Debug)1LogDebug用於開發除錯,生產環境需謹慎使用。
資訊 (Information)2LogInformation追蹤應用程式一般流程。
警告 (Warning)3LogWarning處理異常或意外事件,但不影響程式運作。
錯誤 (Error)4LogError無法處理的例外情況。
重要 (Critical)5LogCritical需要立即處理的嚴重故障。
None6完全停用所有 LOG。

基本注入與設定

WebApplication.CreateBuilder() 預設已載入 Console、Debug 與 EventSource 等 Provider。若需調整,可透過 builder.Logging 進行設定。

什麼情況下會遇到這個問題:當你需要自訂日誌輸出目標或清除預設的 Provider 時。

csharp
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// 清除預設 Provider 並重新設定
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();

使用 Appsettings.json 控制日誌等級

透過設定檔可以針對不同命名空間(Category)設定日誌等級,實現細粒度的控制。

什麼情況下會遇到這個問題:當你希望在生產環境中隱藏特定 Service 的詳細日誌,僅保留 Warning 以上等級時。

json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "LoggerTest.Services.TestService1": "Warning"
    }
  }
}
  • 若需針對多個命名空間套用相同設定,可使用萬用字元,例如 "*.Services": "Warning"

使用 Logger.IsEnabled 提高效能

在執行昂貴操作(如資料庫查詢)前,應先檢查該等級是否啟用。

什麼情況下會遇到這個問題:當日誌內容需要經過複雜計算或大量資料處理才能產出時。

csharp
if (logger.IsEnabled(LogLevel.Information)) {
    int processedRecords = await database.GetProcessedRecordsCount();
    logger.LogInformation("系統已完成資料更新,共處理 {Count} 筆資料。", processedRecords);
}

結構化日誌的優勢

結構化日誌透過模板與參數分離,避免了執行期的字串串接,並能將資料以 JSON 格式輸出,方便後續分析。

什麼情況下會遇到這個問題:當你需要將日誌匯入 ELK 或其他集中式日誌分析系統時。

csharp
// 正確的結構化日誌寫法
logger.LogInformation("使用者 {UserId} 已登入系統", user.Id);

// 輸出 JSON 格式
builder.Logging.AddJsonConsole();

使用 LoggerMessage.Define 與 Source Generator

傳統的 LogInformation 呼叫會產生 object[] 配置與 Boxing 成本。使用 [LoggerMessage] Source Generator 可在編譯期產生高效的強型別委派。

什麼情況下會遇到這個問題:當應用程式對效能要求極高,且有大量日誌輸出需求時。

使用 [LoggerMessage] 範例

csharp
public partial class UserService {
    private readonly ILogger<UserService> logger;

    public UserService(ILogger<UserService> logger) => this.logger = logger;

    public void Login(User user) => LogUserLoggedIn(user.Id, user.Department);

    [LoggerMessage(
        EventId = 1001,
        Level = LogLevel.Information,
        Message = "使用者 {UserId} 已登入系統,所屬部門: {Department}"
    )]
    private partial void LogUserLoggedIn(string userId, string department);
}
  • 效能優勢:編譯期自動產生 IsEnabled 檢查,呼叫路徑無 object[] 配置,且無 Boxing 成本。
  • 命名建議:使用 Log 開頭的動詞短句,等級由 Attribute 控制,參數名稱需與 Message 模板中的 {Placeholder} 一致。

異動歷程

    • 初版文件建立。
    • 新增「使用 LoggerMessage.Define」與「使用 LoggerMessage Source Generator」章節,並加入範例專案連結。